<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">

      <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"/>
    <title>陀飞轮-大前端技术分享博客</title>
  </head>
  <style>
      html, body {
          overflow: hidden;
          margin: 0;
          padding: 0;
          width: 100%;
          height: 100%;
          background:#000;
      }
      canvas {
          position: absolute;
          width: 100%;
          height: 100%;
          background:#000;
          cursor: pointer;
          z-index: 3;
      }
      #app{
          position: absolute;
          width: 100%;
          height: 100%;
          z-index: 4;
      }
  </style>
  <body>
  <script>
    {
      const perlin = {
        init () {
          this.p = new Uint8Array(512);
          this.reset();
        },
        reset() {
          const p = new Uint8Array(256);
          for (let i = 0; i < 256; i++) p[i] = i;
          for (let i = 255; i > 0; i--) {
            const n = Math.floor((i + 1) * Math.random());
            [p[i], p[n]] = [p[n], p[i]];
          }
          for (let i = 0; i < 512; i++) this.p[i] = p[i & 255];
        },
        lerp(t, a, b) {
          return a + t * (b - a);
        },
        grad2d(i, x, y) {
          const v = (i & 1) === 0 ? x : y;
          return (i & 2) === 0 ? -v : v;
        },
        noise2d(x2d, y2d) {
          const X = Math.floor(x2d) & 255;
          const Y = Math.floor(y2d) & 255;
          const x = x2d - Math.floor(x2d);
          const y = y2d - Math.floor(y2d);
          const fx = (3 - 2 * x) * x * x;
          const fy = (3 - 2 * y) * y * y;
          const p0 = this.p[X] + Y;
          const p1 = this.p[X + 1] + Y;
          return this.lerp(
            fy,
            this.lerp(
              fx,
              this.grad2d(this.p[p0], x, y),
              this.grad2d(this.p[p1], x - 1, y)
            ),
            this.lerp(
              fx,
              this.grad2d(this.p[p0 + 1], x, y - 1),
              this.grad2d(this.p[p1 + 1], x - 1, y - 1)
            )
          );
        }
      };
/////////////////////////////////////////////////////////////////
      const canvas = {
        init() {
          this.elem = document.createElement("canvas");
          document.body.appendChild(this.elem);
          this.width = this.elem.width = this.elem.offsetWidth;
          this.height = this.elem.height = this.elem.offsetHeight;
          return this.elem.getContext("2d");
        }
      };
/////////////////////////////////////////////////////////////////
      const webgl = {
        init(canvas, options) {
          this.elem = document.createElement("canvas");
          this.gl = (
            this.elem.getContext("webgl", options) ||
            this.elem.getContext("experimental-webgl", options)
          );
          if (!this.gl) return false;
          const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
          this.gl.shaderSource(vertexShader, `
				precision highp float;
				attribute vec3 aPosition;
				uniform vec2 uResolution;
				void main() {
					gl_PointSize = 1.0;
					gl_Position = vec4(
						( aPosition.x / uResolution.x * 2.0) - 1.0,
						(-aPosition.y / uResolution.y * 2.0) + 1.0,
						0.0,
						1.0
					);
				}`);
          this.gl.compileShader(vertexShader);
          const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
          this.gl.shaderSource(fragmentShader, `
				precision highp float;
				void main() {
					gl_FragColor = vec4(0.2, 0.3, 1.0, 1.0);
				}`
          );
          this.gl.compileShader(fragmentShader);
          const program = this.gl.createProgram();
          this.gl.attachShader(program, vertexShader);
          this.gl.attachShader(program, fragmentShader);
          this.gl.linkProgram(program);
          this.gl.useProgram(program);
          this.aPosition = this.gl.getAttribLocation(program, "aPosition");
          this.gl.enableVertexAttribArray(this.aPosition);
          this.positionBuffer = this.gl.createBuffer();
          this.elem.width = canvas.width;
          this.elem.height = canvas.height;
          const uResolution = this.gl.getUniformLocation(program, "uResolution");
          this.gl.enableVertexAttribArray(uResolution);
          this.gl.uniform2f(uResolution, canvas.width, canvas.height);
          this.gl.viewport(
            0,
            0,
            this.gl.drawingBufferWidth,
            this.gl.drawingBufferHeight
          );
          return this.gl;
        },
        drawBuffer(data, num) {
          this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
          this.gl.vertexAttribPointer(this.aPosition, 2, this.gl.FLOAT, false, 0, 0);
          this.gl.bufferData(
            this.gl.ARRAY_BUFFER,
            data,
            this.gl.DYNAMIC_DRAW
          );
          this.gl.drawArrays(this.gl.GL_POINTS, 0, num);
        }
      };
      const ctx = canvas.init();
      const gl = webgl.init(canvas, {
        alpha: false,
        stencil: false,
        antialias: false,
        depth: false,
      });
      perlin.init();
      const nParticles = 3000;
      const velocities = new Float32Array(nParticles * 2);
      const particles = new Float32Array(nParticles * 2);
      let frame = 0;
      for (let i = 0; i < nParticles; i++) {
        const p = i * 2;
        particles[p+0] = Math.random() * canvas.width;
        particles[p+1] = Math.random() * canvas.height;
      }

      const run = () => {
        requestAnimationFrame(run);
        frame++;
        gl.clear(gl.COLOR_BUFFER_BIT);
        ctx.globalCompositeOperation = "source-over";
        ctx.fillStyle = "rgba(0, 0, 0, 0.025)";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.globalCompositeOperation = "lighter";
        for (let i = 0; i < nParticles; i++) {
          const p = i * 2;
          let n = 80 * perlin.noise2d(particles[p+0] * 0.001, particles[p+1] * 0.001);
          velocities[p+0] += 0.1 * Math.cos(n);
          velocities[p+1] += 0.1 * Math.sin(n);
          particles[p+0]  += (velocities[p+0] *= 0.99);
          particles[p+1]  += (velocities[p+1] *= 0.99);
          particles[p+0]   = (canvas.width  + particles[p+0]) % canvas.width;
          particles[p+1]   = (canvas.height + particles[p+1]) % canvas.height;
        }
        webgl.drawBuffer(particles, nParticles);
        if (frame > 30) ctx.drawImage(webgl.elem, 0, 0);
      };
      requestAnimationFrame(run);
/////////////////////////////////////////////////////////////////
      ["click", "touchdown"].forEach(event => {
        document.addEventListener(event, e => perlin.reset(), false);
      });
    }
  </script>

    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
